//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"

//SAGITTARIUS headers:
#include "sagittarius/io/io.h"
#include <math.h>

void sg_writeInt(int fd, int n) {
    int mag;
    int i;
    char digits[64];
    if(n == 0) {
        sg_write(fd, "0", 1);
        return;
    }
    if(n < 0) {
        sg_write(fd, "-", 1);
        n = -n;
    }
    mag = log10((double)n);
    AT(mag < 64);
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (unsigned int)pow(10,mag-i))%10 + 0x30;
    }
    sg_write(fd, digits, i);
}

void sg_writeUInt(int fd, unsigned int n) {
    int mag;
    int i;
    char digits[64];
    if(n == 0) {
        sg_write(fd, "0", 1);
        return;
    }
    mag = log10((double)n);
    AT(mag < 64);
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (int)pow(10,mag-i))%10 + 0x30;
    }
    //digits[i] = '\0';
    sg_write(fd, digits, i);
}

void sg_writeInt64(int fd, int64 n) {
    int mag;
    int i;
    char digits[64];
    if(n == 0) {
        sg_write(fd, "0", 1);
        return;
    }
    if(n < 0) {
        sg_write(fd, "-", 1);
        n = -n;
    }
    mag = log10((double)n);
    AT(mag < 64);
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (int64)pow(10,mag-i))%10 + 0x30;
    }
    sg_write(fd, digits, i);
}

void sg_writeUInt64(int fd, uint64 n) {
    int mag;
    int i;
    char digits[64];
    if(n == 0) {
        sg_write(fd, "0", 1);
        return;
    }
    mag = log10((double)n);
    AT(mag < 64);
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (uint64)pow(10,mag-i))%10 + 0x30;
    }
    //digits[i] = '\0';
    sg_write(fd, digits, i);
}

double log16(double x) {
    return log(x) / log(16.0);
}

void sg_writeUInt32Hex(int fd, uint32 n) {
    int i;
    char digits[64];
    for(i=0; i<=8; ++i) {
        digits[i] = (n >> ((7-i)*4)) & 0xf;
        if(digits[i] < 10)
            digits[i] += 0x30;
        else
            digits[i] += 0x57;
    }
    digits[8] = '\0';
    sg_write(fd, digits, i);
}

void sg_writeUInt64Hex(int fd, uint64 n) {
    int mag;
    int i;
    char digits[64];
    if(n == 0) {
        sg_write(fd, "0", 1);
        return;
    }
    mag = log16((double)n);
    AT(mag < 64);
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (uint64)pow(16,mag-i))%16;
        if(digits[i] < 10)
            digits[i] += 0x30;
        else
            digits[i] += 0x57;
    }
    //digits[i] = '\0';
    sg_write(fd, digits, i);
}

#if 0
void sg_writeInt2(int n) {
    int mag;
    int i;
    char digits[128];
    sg_write(2, "x", sizeof("x"));
    if(n == 0) {
        sg_write(2, "0", 1);
        return;
    }
    if(n < 0) {
        sg_write(2, "-", 1);
        n = -n;
    }
    sg_write(2, "y", sizeof("x"));
    printf("%d\n", n);
    mag = log10((double)n);
    //mag = 10;
    sg_write(2, "1", sizeof("x"));
    for(i=0; i<=mag; ++i) {
        digits[i] = (n / (int)pow(10,mag-i))%10 + 0x30;
    }
    sg_write(2, "z", sizeof("x"));
    //digits[i] = '\0';
    sg_write(2, digits, i);
}
#endif

void sg_writeInt2(int n) {
    sg_writeInt(2, n);
}

void sg_writeUInt2(unsigned int n) {
    sg_writeUInt(2, n);
}

void sg_writeNibble(int fd, int nibb) {
    switch(nibb) {
        case 0:
            SG_WRITE(fd, "0");
            return;
        case 1:
            SG_WRITE(fd, "1");
            return;
        case 2:
            SG_WRITE(fd, "2");
            return;
        case 3:
            SG_WRITE(fd, "3");
            return;
        case 4:
            SG_WRITE(fd, "4");
            return;
        case 5:
            SG_WRITE(fd, "5");
            return;
        case 6:
            SG_WRITE(fd, "6");
            return;
        case 7:
            SG_WRITE(fd, "7");
            return;
        case 8:
            SG_WRITE(fd, "8");
            return;
        case 9:
            SG_WRITE(fd, "9");
            return;
        case 10:
            SG_WRITE(fd, "a");
            return;
        case 11:
            SG_WRITE(fd, "b");
            return;
        case 12:
            SG_WRITE(fd, "c");
            return;
        case 13:
            SG_WRITE(fd, "d");
            return;
        case 14:
            SG_WRITE(fd, "e");
            return;
        case 15:
            SG_WRITE(fd, "f");
            return;
        default:
            AN(0 && "Unrecognized nibble");
            return;
    }
}

void sg_writeByteHex(int fd, uint8 byte) {
    sg_writeNibble(fd, (byte&0xF0) >> 4); //upper nibble
    sg_writeNibble(fd, byte&0x0F); //lower nibble
}

void sg_writeMemSegHex(int fd, void* addr, size_t nbytes) {
    size_t i;
    uint8 byte;
    SG_WRITE(fd, "0x");
    for(i=0;i<nbytes;++i) {
        byte = (uint8)*((uint8*)addr+i);
        sg_writeByteHex(fd, byte);
    }
}

void sg_writeCStr(int fd, const char* str) {
    size_t i = 0;
    const char* c = str;
    while(*c != '\0') {
        ++i;
        ++c;
    }
    sg_write(fd, (void*)str, i);
}

void sg_writeChar(int fd, char c) {
    sg_write(fd, &c, 1);
}

void sg_writeUStr(int fd, const UnicodeCChar* buf) {
    //taken from CUnicode.c: UCEncode
    size_t n_bytes;
    
    while(*buf != 0x00)
    {
        n_bytes = UCgetNBytes(*buf);
        switch(n_bytes)
        {
            case 1:
                sg_writeChar(fd, (char)(*buf & 0x7F));
                ++buf;
                break;
            case 2:
                sg_writeChar(fd, (char)((*buf >> 6) & 0x1F) | 0xC0);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            case 3:
                sg_writeChar(fd, (char)((*buf >> 12) & 0x0F) | 0xE0);
                sg_writeChar(fd, (char)((*buf >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            case 4:
                sg_writeChar(fd, (char)((*buf >> 18) & 0x07) | 0xF0);
                sg_writeChar(fd, (char)((*buf >> 12) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)((*buf >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            default:
                AN(0, "sg_writeUStr: Invalid number of bytes");
        }
    }
}

void sg_writeUStrl(int fd, const UnicodeCChar* buf, const size_t len) {
    //taken from CUnicode.c: UCEncode
    const UnicodeCChar* stop = buf+len;
    size_t n_bytes;
    
    while(buf != stop)
    {
        n_bytes = UCgetNBytes(*buf);
        switch(n_bytes)
        {
            case 1:
                sg_writeChar(fd, (char)(*buf & 0x7F));
                ++buf;
                break;
            case 2:
                sg_writeChar(fd, (char)((*buf >> 6) & 0x1F) | 0xC0);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            case 3:
                sg_writeChar(fd, (char)((*buf >> 12) & 0x0F) | 0xE0);
                sg_writeChar(fd, (char)((*buf >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            case 4:
                sg_writeChar(fd, (char)((*buf >> 18) & 0x07) | 0xF0);
                sg_writeChar(fd, (char)((*buf >> 12) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)((*buf >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(*buf & 0x3F) | 0x80);
                ++buf;
                break;
            default:
                AN(0, "sg_writeUStr: Invalid number of bytes");
        }
    }
}

void sg_writeUChar(int fd, const UnicodeCChar x) {
    sg_writeUStrl(fd, &x, 1);
}

#if SAGITTARIUS_LINK_WITH_GMP
void sg_writeGMPUStr(int fd, GMPUnicodeChar* buf) {
    size_t n_bytes;
    
    while(SG_GET_BIGNUM_UI(*buf) != 0x00)
    {
        n_bytes = UCGMPgetNBytes(*buf);
        switch(n_bytes)
        {
            case 1:
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x7F));
                ++buf;
                break;
            case 2:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x1F) | 0xC0);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            case 3:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x0F) | 0xE0);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            case 4:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 18) & 0x07) | 0xF0);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            default:
                AN(0, "sg_writeUStr: Invalid number of bytes");
        }
    }
}

void sg_writeGMPUStrl(int fd, GMPUnicodeChar* buf, const size_t len) {
    GMPUnicodeChar* stop = buf+len;
    size_t n_bytes;
    
    while(buf != stop)
    {
        n_bytes = UCGMPgetNBytes(*buf);
        switch(n_bytes)
        {
            case 1:
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x7F));
                ++buf;
                break;
            case 2:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x1F) | 0xC0);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            case 3:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x0F) | 0xE0);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            case 4:
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 18) & 0x07) | 0xF0);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 12) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)((SG_GET_BIGNUM_UI(*buf) >> 6) & 0x3F) | 0x80);
                sg_writeChar(fd, (char)(SG_GET_BIGNUM_UI(*buf) & 0x3F) | 0x80);
                ++buf;
                break;
            default:
                AN(0, "sg_writeUStr: Invalid number of bytes");
        }
    }
}
#endif

void sg_writePtr(int fd, void* ptr) {
    #if SAGITTARIUS_ENDIAN == SAGITTARIUS_LITTLE_ENDIAN
        #if SG_PTR_SIZE == 8
            SG_WRITE(fd, "0x");
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 56));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 48));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 40));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 32));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 24));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 16));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr >> 8));
            sg_writeByteHex(fd, (uint8)((intptr_t)ptr));
        #endif
    #endif
}